﻿using System;
using System.Collections.Generic;
using System.Linq;
using Patterns.Interfaces;

namespace Patterns.Aggregation
{
    public class Aggregator<TDataType>
    {
        #region Fields
        private readonly Dictionary<string, IAggregator> _aggregates;
        private readonly Dictionary<string, TimeSpan> _aggregateTimeSpans;
        private readonly SortedDictionary<DateTime, TDataType> _data;
        private readonly IObservable<TDataType> _observable;
        #endregion

        #region Constructor
        public Aggregator(IObservable<TDataType> observable)
        {
            _observable = observable;
            _observable.Subscribe(OnNext);
            _aggregates = new Dictionary<string, IAggregator>();
            _data = new SortedDictionary<DateTime, TDataType>();
            _aggregateTimeSpans= new Dictionary<string, TimeSpan>();
        }
        #endregion

        #region Public methods
        public void Register<TAccumulate, TTerminate, TAggregator>(string instanceId)
        {
            Type d1 = typeof(TAggregator);
            Type[] typeArgs = { typeof(TAccumulate), typeof(TTerminate) };
            Type constructed = d1.MakeGenericType(typeArgs);
            _aggregates[instanceId] = Activator.CreateInstance(constructed) as IAggregator;
        }

        public void Register<TAggregator>(string instanceId, TimeSpan timeSpan)
        {
            _aggregateTimeSpans[instanceId] = timeSpan;
            _aggregates[instanceId] = Activator.CreateInstance(typeof(TAggregator)) as IAggregator;
        }

        public void Register<TAggregator>(string instanceId)
        {
            _aggregates[instanceId] = Activator.CreateInstance(typeof(TAggregator)) as IAggregator;
        }

        public TAggregator Resolve<TAggregator>(string instanceId) where TAggregator : IAggregator
        {
            IAggregator instance;
            _aggregates.TryGetValue(instanceId, out instance);
            var result = (TAggregator)instance;
            return result;
        }
        #endregion

        #region Private members
        private void OnNext(TDataType newData)
        {
            var timeStamp = DateTime.UtcNow;
            _data[timeStamp] = newData;
            foreach (var pair in _aggregates)
            {
                pair.Value.Accumulate(newData);
                TimeSpan timeSlot;
                if (_data.Keys.Any() && _aggregateTimeSpans.TryGetValue(pair.Key, out timeSlot))
                {
                    DateTime? firstKey = _data.Keys.First();
                    var currentTimeSpan = timeStamp - firstKey;
                    if (currentTimeSpan > timeSlot)
                    {
                        pair.Value.Dissipate(_data[firstKey.Value]);
                        _data.Remove(firstKey.Value);
                    }
                }
            }
                
        }
        #endregion
    }
}
